home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / JFC.bin / DefaultTreeSelectionModel.java < prev    next >
Text File  |  1998-06-30  |  31KB  |  1,051 lines

  1. /*
  2.  * @(#)DefaultTreeSelectionModel.java    1.18 98/02/02
  3.  * 
  4.  * Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
  5.  * 
  6.  * This software is the confidential and proprietary information of Sun
  7.  * Microsystems, Inc. ("Confidential Information").  You shall not
  8.  * disclose such Confidential Information and shall use it only in
  9.  * accordance with the terms of the license agreement you entered into
  10.  * with Sun.
  11.  * 
  12.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
  13.  * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  14.  * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  15.  * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
  16.  * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
  17.  * THIS SOFTWARE OR ITS DERIVATIVES.
  18.  * 
  19.  */
  20.  
  21. package com.sun.java.swing.tree;
  22.  
  23. import java.beans.PropertyChangeSupport;
  24. import java.beans.PropertyChangeListener;
  25. import java.io.*;
  26. import java.util.BitSet;
  27. import java.util.EventListener;
  28. import java.util.Vector;
  29. import com.sun.java.swing.event.*;
  30. import com.sun.java.swing.DefaultListSelectionModel;
  31.  
  32. /**
  33.  * Implementation of TreeSelectionModel.  Listeners are notified whenever
  34.  * the paths in the selection change, not the rows. In order
  35.  * to be able to track row changes you may wish to become a listener 
  36.  * for expansion events on the tree and test for changes from there.
  37.  * <p>
  38.  * Warning: serialized objects of this class will not be compatible with
  39.  * future swing releases.  The current serialization support is appropriate 
  40.  * for short term storage or RMI between Swing1.0 applications.  It will
  41.  * not be possible to load serialized Swing1.0 objects with future releases
  42.  * of Swing.  The JDK1.2 release of Swing will be the compatibility
  43.  * baseline for the serialized form of Swing objects.
  44.  *
  45.  * @version 1.18 02/02/98
  46.  * @author Scott Violet
  47.  */
  48. public class DefaultTreeSelectionModel extends Object implements Cloneable, Serializable, TreeSelectionModel
  49. {
  50.     /** Property name for selectionMode. */
  51.     public static final String          SELECTION_MODE_PROPERTY = "selectionMode";
  52.  
  53.     /** Used to messaged registered listeners. */
  54.     protected PropertyChangeSupport     changeSupport;
  55.  
  56.     /** Paths that are currently selected.  Will be null if nothing is
  57.       * currently selected. */
  58.     protected TreePath[]                selection;
  59.  
  60.     /** Event listener list. */
  61.     protected EventListenerList   listenerList = new EventListenerList();
  62.  
  63.     /** Provides a row for a given path. */
  64.     transient protected RowMapper               rowMapper;
  65.  
  66.     /** Handles maintaining the list selection model. */
  67.     protected DefaultListSelectionModel     listSelectionModel;
  68.  
  69.     /** Mode for the selection, will be either SINGLE_TREE_SELECTION,
  70.      * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION.
  71.      */
  72.     protected int                           selectionMode;
  73.  
  74.     /** Last path that was added. */
  75.     protected TreePath                      leadPath;
  76.     /** Index of the lead path in selection. */
  77.     protected int                           leadIndex;
  78.     /** Lead row. */
  79.     protected int                           leadRow;
  80.  
  81.     public DefaultTreeSelectionModel() {
  82.     listSelectionModel = new DefaultListSelectionModel();
  83.     selectionMode = DISCONTIGUOUS_TREE_SELECTION;
  84.     leadIndex = leadRow = -1;
  85.     }
  86.  
  87.     /**
  88.      * Sets the RowMapper instance.  This instance is used to determine
  89.      * what row corresponds to what path.
  90.      */
  91.     public void setRowMapper(RowMapper newMapper) {
  92.     rowMapper = newMapper;
  93.     resetRowSelection();
  94.     }
  95.  
  96.     /**
  97.      * Returns the RowMapper instance that is able to map a path to a
  98.      * row.
  99.      */
  100.     public RowMapper getRowMapper() {
  101.     return rowMapper;
  102.     }
  103.  
  104.     /**
  105.      * Sets the selection model, which must be one of SINGLE_TREE_SELECTION,
  106.      * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION.
  107.      */
  108.     public void setSelectionMode(int mode) {
  109.     int            oldMode = selectionMode;
  110.  
  111.     selectionMode = mode;
  112.     if(selectionMode != TreeSelectionModel.SINGLE_TREE_SELECTION &&
  113.        selectionMode != TreeSelectionModel.CONTIGUOUS_TREE_SELECTION &&
  114.        selectionMode != TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
  115.         selectionMode = TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION;
  116.     if(oldMode != selectionMode && changeSupport != null)
  117.         changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY,
  118.                          new Integer(oldMode),
  119.                          new Integer(selectionMode));
  120.     }
  121.  
  122.     /**
  123.      * Returns the selection mode.
  124.      */
  125.     public int getSelectionMode() {
  126.     return selectionMode;
  127.     }
  128.  
  129.     /**
  130.       * Sets the selection to path.  If this represents a change, then
  131.       * the TreeSelectionListeners are notified.
  132.       *
  133.       * @param path new path to select
  134.       */
  135.     public void setSelectionPath(TreePath path) {
  136.     if(path == null)
  137.         setSelectionPaths(null);
  138.     else {
  139.         TreePath[]          newPaths = new TreePath[1];
  140.  
  141.         newPaths[0] = path;
  142.         setSelectionPaths(newPaths);
  143.     }
  144.     }
  145.  
  146.     /**
  147.       * Sets the selection to the paths in paths.  If this represents a
  148.       * change the TreeSelectionListeners are notified.  Potentially
  149.       * paths will be held by the reciever, in other words don't change
  150.       * any of the objects in the array once passed in.
  151.       *
  152.       * @param paths new selection.
  153.       */
  154.     public void setSelectionPaths(TreePath[] pPaths) {
  155.     boolean        differs = false;
  156.     int            newCount, newCounter, oldCount, oldCounter;
  157.     TreePath[]     paths = pPaths;
  158.  
  159.     if(paths == null)
  160.         newCount = 0;
  161.     else
  162.         newCount = paths.length;
  163.     if(selection == null)
  164.         oldCount = 0;
  165.     else
  166.         oldCount = selection.length;
  167.     if((newCount + oldCount) != 0) {
  168.         if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION) {
  169.         /* If single selection and more than one path, only allow
  170.            first. */
  171.         if(newCount > 1) {
  172.             paths = new TreePath[1];
  173.             paths[0] = pPaths[0];
  174.             newCount = 1;
  175.         }
  176.         }
  177.         else if(selectionMode ==
  178.             TreeSelectionModel.CONTIGUOUS_TREE_SELECTION) {
  179.         /* If contiguous selection and paths aren't contiguous,
  180.            only select the first path item. */
  181.         if(newCount > 0 && !arePathsContiguous(paths)) {
  182.             paths = new TreePath[1];
  183.             paths[0] = pPaths[0];
  184.             newCount = 1;
  185.         }
  186.         }
  187.  
  188.         boolean          found;
  189.         int              validCount = 0;
  190.         TreePath         beginLeadPath = leadPath;
  191.         Vector           cPaths = new Vector(newCount + oldCount);
  192.  
  193.         leadPath = null;
  194.         /* Find the paths that are new. */
  195.         for(newCounter = 0; newCounter < newCount; newCounter++) {
  196.         found = false;
  197.         if(paths[newCounter] != null) {
  198.             validCount++;
  199.             for(oldCounter = 0; oldCounter < oldCount; oldCounter++) {
  200.             if(selection[oldCounter] != null &&
  201.                selection[oldCounter].equals(paths[newCounter])) {
  202.                 selection[oldCounter] = null;
  203.                 oldCounter = oldCount;
  204.                 found = true;
  205.             }
  206.             }
  207.             if(!found)
  208.             cPaths.addElement(new PathPlaceHolder
  209.                       (paths[newCounter], true));
  210.             if(leadPath == null)
  211.             leadPath = paths[newCounter];
  212.         }
  213.         }
  214.  
  215.         /* Get the paths that were selected but no longer selected. */
  216.         for(oldCounter = 0; oldCounter < oldCount; oldCounter++)
  217.         if(selection[oldCounter] != null)
  218.             cPaths.addElement(new PathPlaceHolder
  219.                       (selection[oldCounter], false));
  220.  
  221.         /* If the validCount isn't equal to newCount it means there
  222.            are some null in paths, remove them and set selection to
  223.            the new path. */
  224.         if(validCount == 0)
  225.         selection = null;
  226.         else if (validCount != newCount) {
  227.         selection = new TreePath[validCount];
  228.  
  229.         for(newCounter = 0, validCount = 0; newCounter < newCount;
  230.             newCounter++)
  231.             if(paths[newCounter] != null)
  232.             selection[validCount++] = paths[newCounter];
  233.         }
  234.         else {
  235.         selection = new TreePath[paths.length];
  236.         System.arraycopy(paths, 0, selection, 0, paths.length);
  237.         }
  238.  
  239.         if(selection != null)
  240.         insureUniqueness();
  241.  
  242.         updateLeadIndex();
  243.  
  244.         resetRowSelection();
  245.         /* Notify of the change. */
  246.         if(cPaths.size() > 0)
  247.         notifyPathChange(cPaths, beginLeadPath);
  248.     }
  249.     }
  250.  
  251.     /**
  252.       * Adds path to the current selection.  If path is not currently
  253.       * in the selection the TreeSelectionListeners are notified.
  254.       *
  255.       * @param path the new path to add to the current selection.
  256.       */
  257.     public void addSelectionPath(TreePath path) {
  258.     if(path != null) {
  259.         TreePath[]            toAdd = new TreePath[1];
  260.  
  261.         toAdd[0] = path;
  262.         addSelectionPaths(toAdd);
  263.     }
  264.     }
  265.  
  266.     /**
  267.       * Adds paths to the current selection.  If any of the paths in 
  268.       * paths are not currently in the selection the TreeSelectionListeners
  269.       * are notified.
  270.       *
  271.       * @param path the new path to add to the current selection.
  272.       */
  273.     public void addSelectionPaths(TreePath[] paths) {
  274.     int       newPathLength = ((paths == null) ? 0 : paths.length);
  275.  
  276.     if(newPathLength > 0) {
  277.         if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION &&
  278.            selection != null && selection.length > 0)
  279.         setSelectionPaths(paths);
  280.         else if(selectionMode == TreeSelectionModel.
  281.             CONTIGUOUS_TREE_SELECTION && !canPathsBeAdded(paths)) {
  282.         if(arePathsContiguous(paths))
  283.             setSelectionPaths(paths);
  284.         else {
  285.             TreePath[]          newPaths = new TreePath[1];
  286.  
  287.             newPaths[0] = paths[0];
  288.             setSelectionPaths(newPaths);
  289.         }
  290.         }
  291.         else {
  292.         boolean           didCopyPaths, inSelection;
  293.         int               counter, validCount;
  294.         int               oldCount, oldCounter;
  295.         TreePath          beginLeadPath = leadPath;
  296.         Vector            cPaths = null;
  297.  
  298.         if(selection == null)
  299.             oldCount = 0;
  300.         else
  301.             oldCount = selection.length;
  302.         didCopyPaths = false;
  303.         leadPath = null;
  304.         /* Determine the paths that aren't currently in the
  305.            selection. */
  306.         for(counter = 0, validCount = 0; counter < paths.length;
  307.             counter++) {
  308.             if(paths[counter] != null) {
  309.             inSelection = false;
  310.             for (oldCounter = 0; oldCounter < oldCount;
  311.                  oldCounter++) {
  312.                 if(paths[counter].equals(selection[oldCounter])) {
  313.                 oldCounter = oldCount;
  314.                 if(!didCopyPaths) {
  315.                     /* Copy the paths so that we can mess with
  316.                        the array. */
  317.                     TreePath[]      copiedPaths;
  318.  
  319.                     copiedPaths = new TreePath[paths.length];
  320.                     System.arraycopy(paths, 0, copiedPaths,
  321.                              0, paths.length);
  322.                     paths = copiedPaths;
  323.                     didCopyPaths = true;
  324.                 }
  325.                 paths[counter] = null;
  326.                 inSelection = true;
  327.                 }
  328.             }
  329.             if(!inSelection) {
  330.                 validCount++;
  331.                 if(cPaths == null)
  332.                 cPaths = new Vector();
  333.                 cPaths.addElement(new PathPlaceHolder
  334.                           (paths[counter], true));
  335.             }
  336.             if(leadPath == null)
  337.                 leadPath = paths[counter];
  338.             }
  339.         }
  340.  
  341.         if(leadPath == null)
  342.             leadPath = beginLeadPath;
  343.  
  344.         if(validCount > 0) {
  345.             TreePath         newSelection[] = new TreePath[oldCount +
  346.                                   validCount];
  347.  
  348.             /* And build the new selection. */
  349.             if(oldCount > 0)
  350.             System.arraycopy(selection, 0, newSelection, 0,
  351.                      oldCount);
  352.             if(validCount != paths.length) {
  353.             /* Some of the paths in paths are already in
  354.                the selection. */
  355.             int           validCounter;
  356.  
  357.             for(counter = validCounter = 0; counter < paths.length;
  358.                 counter++) {
  359.                 if (paths[counter] != null)
  360.                 newSelection[oldCount + validCounter++] =
  361.                     paths[counter];
  362.             }
  363.             }
  364.             else
  365.             System.arraycopy(paths, 0, newSelection, oldCount,
  366.                      validCount);
  367.  
  368.             selection = newSelection;
  369.  
  370.             insureUniqueness();
  371.  
  372.             updateLeadIndex();
  373.  
  374.             resetRowSelection();
  375.  
  376.             notifyPathChange(cPaths, beginLeadPath);
  377.         }
  378.         else
  379.             leadPath = beginLeadPath;
  380.         }
  381.     }
  382.     }
  383.  
  384.     /**
  385.       * Removes path from the selection.  If path is in the selection
  386.       * The TreeSelectionListeners are notified.
  387.       *
  388.       * @param path the path to remove from the selection.
  389.       */
  390.     public void removeSelectionPath(TreePath path) {
  391.     if(path != null) {
  392.         TreePath[]             rPath = new TreePath[1];
  393.  
  394.         rPath[0] = path;
  395.         removeSelectionPaths(rPath);
  396.     }
  397.     }
  398.  
  399.     /**
  400.       * Removes paths from the selection.  If any of the paths in paths
  401.       * are in the selection the TreeSelectionListeners are notified.
  402.       *
  403.       * @param path the path to remove from the selection.
  404.       */
  405.     public void removeSelectionPaths(TreePath[] paths) {
  406.     if (paths != null && selection != null && paths.length > 0) {
  407.         if(!canPathsBeRemoved(paths)) {
  408.         /* Could probably do something more interesting here! */
  409.         clearSelection();
  410.         }
  411.         else {
  412.         int         oldCount, oldCounter;
  413.         int         removeCount, removeCounter;
  414.         int         toRemoveCount = 0;
  415.         TreePath    beginLeadPath = leadPath;
  416.         Vector      pathsToRemove = null;
  417.  
  418.         oldCount = selection.length;
  419.  
  420.         /* Find the paths that can be removed. */
  421.         for (removeCounter = 0; removeCounter < paths.length;
  422.              removeCounter++) {
  423.             if(paths[removeCounter] != null) {
  424.             if(leadPath != null &&
  425.                leadPath.equals(paths[removeCounter]))
  426.                 leadPath = null;
  427.             for(oldCounter = 0; oldCounter < oldCount;
  428.                 oldCounter++) {
  429.                 if(paths[removeCounter].equals
  430.                    (selection[oldCounter])){
  431.                 selection[oldCounter] = null;
  432.                 oldCounter = oldCount;
  433.                 if(pathsToRemove == null)
  434.                     pathsToRemove = new Vector(paths.length);
  435.                 if(!pathsToRemove.contains
  436.                    (paths[removeCounter]))
  437.                     pathsToRemove.addElement
  438.                     (new PathPlaceHolder
  439.                      (paths[removeCounter], false));
  440.                 }
  441.             }
  442.             }
  443.         }
  444.         if(pathsToRemove != null) {
  445.             removeCount = pathsToRemove.size();
  446.             if(removeCount == selection.length)
  447.             selection = null;
  448.             else {
  449.             int                  validCount = 0;
  450.             TreePath[]          newSelection;
  451.  
  452.             newSelection = new TreePath[selection.length -
  453.                            removeCount];
  454.             for(oldCounter = 0; oldCounter < oldCount; 
  455.                 oldCounter++)
  456.                 if(selection[oldCounter] != null)
  457.                 newSelection[validCount++] =
  458.                     selection[oldCounter];
  459.             selection = newSelection;
  460.             }
  461.  
  462.             if(leadPath == null && selection != null)
  463.             leadPath = selection[0];
  464.  
  465.             updateLeadIndex();
  466.  
  467.             resetRowSelection();
  468.  
  469.             notifyPathChange(pathsToRemove, beginLeadPath);
  470.         }
  471.         }
  472.     }
  473.     }
  474.  
  475.     /**
  476.       * Returns the first path in the selection.
  477.       */
  478.     public TreePath getSelectionPath() {
  479.     if(selection != null)
  480.         return selection[0];
  481.     return null;
  482.     }
  483.  
  484.     /**
  485.       * Returns the paths in the selection.
  486.       */
  487.     public TreePath[] getSelectionPaths() {
  488.     if(selection != null) {
  489.         int                 pathSize = selection.length;
  490.         TreePath[]         result = new TreePath[pathSize];
  491.  
  492.         System.arraycopy(selection, 0, result, 0, pathSize);
  493.         return result;
  494.     }
  495.     return null;
  496.     }
  497.  
  498.     /**
  499.      * Returns the number of paths that are selected.
  500.      */
  501.     public int getSelectionCount() {
  502.     return (selection == null) ? 0 : selection.length;
  503.     }
  504.  
  505.     /**
  506.       * Returns true if the path, path, is in the current selection.
  507.       */
  508.     public boolean isPathSelected(TreePath path) {
  509.     if (selection != null) {
  510.         for (int counter = 0; counter < selection.length; counter++)
  511.         if(selection[counter].equals(path))
  512.             return true;
  513.     }
  514.     return false;
  515.     }
  516.  
  517.     /**
  518.       * Returns true if the selection is currently empty.
  519.       */
  520.     public boolean isSelectionEmpty() {
  521.     return (selection == null);
  522.     }
  523.  
  524.     /**
  525.       * Empties the current selection.  If this represents a change in the
  526.       * current selection, the selection listeners are notified.
  527.       */
  528.     public void clearSelection() {
  529.     if(selection != null) {
  530.         int                    selSize = selection.length;
  531.         boolean[]              newness = new boolean[selSize];
  532.  
  533.         for(int counter = 0; counter < selSize; counter++)
  534.         newness[counter] = false;
  535.  
  536.         TreeSelectionEvent     event = new TreeSelectionEvent
  537.         (this, selection, newness, leadPath, null);
  538.  
  539.         leadPath = null;
  540.         leadIndex = leadRow = -1;
  541.         selection = null;
  542.         resetRowSelection();
  543.         fireValueChanged(event);
  544.     }
  545.     }
  546.  
  547.     /**
  548.       * Adds x to the list of listeners that are notified each time the
  549.       * selection changes.
  550.       *
  551.       * @param x the new listener to be added.
  552.       */
  553.     public void addTreeSelectionListener(TreeSelectionListener x) {
  554.     listenerList.add(TreeSelectionListener.class, x);
  555.     }
  556.  
  557.     /**
  558.       * Removes x from the list of listeners that are notified each time
  559.       * the selection changes.
  560.       *
  561.       * @param x the listener to remove.
  562.       */
  563.     public void removeTreeSelectionListener(TreeSelectionListener x) {
  564.     listenerList.remove(TreeSelectionListener.class, x);
  565.     }
  566.  
  567.     /*
  568.      * Notify all listeners that have registered interest for
  569.      * notification on this event type.  The event instance 
  570.      * is lazily created using the parameters passed into 
  571.      * the fire method.
  572.      * @see EventListenerList
  573.      */
  574.     protected void fireValueChanged(TreeSelectionEvent e) {
  575.     // Guaranteed to return a non-null array
  576.     Object[] listeners = listenerList.getListenerList();
  577.     // TreeSelectionEvent e = null;
  578.     // Process the listeners last to first, notifying
  579.     // those that are interested in this event
  580.     for (int i = listeners.length-2; i>=0; i-=2) {
  581.         if (listeners[i]==TreeSelectionListener.class) {
  582.         // Lazily create the event:
  583.         // if (e == null)
  584.         // e = new ListSelectionEvent(this, firstIndex, lastIndex);
  585.         ((TreeSelectionListener)listeners[i+1]).valueChanged(e);
  586.         }           
  587.     }
  588.     }
  589.  
  590.     /**
  591.       * Returns all of the currently selected rows.
  592.       */
  593.     public int[] getSelectionRows() {
  594.     // This is currently rather expensive.  Needs
  595.     // to be better support from ListSelectionModel to speed this up.
  596.     if(rowMapper != null && selection != null) {
  597.         return rowMapper.getRowsForPaths(selection);
  598.     }
  599.     return null;
  600.     }
  601.  
  602.     /**
  603.       * Gets the first selected row.
  604.       */
  605.     public int getMinSelectionRow() {
  606.     return listSelectionModel.getMinSelectionIndex();
  607.     }
  608.  
  609.     /**
  610.       * Gets the last selected row.
  611.       */
  612.     public int getMaxSelectionRow() {
  613.     return listSelectionModel.getMaxSelectionIndex();
  614.     }
  615.  
  616.     /**
  617.       * Returns true if the row identitifed by row is selected.
  618.       */
  619.     public boolean isRowSelected(int row) {
  620.     return listSelectionModel.isSelectedIndex(row);
  621.     }
  622.  
  623.     /**
  624.      * Recalculates what rows are selected by asking the RowMapper for the
  625.      * row for each path.
  626.      */
  627.     public void resetRowSelection() {
  628.     listSelectionModel.clearSelection();
  629.     if(selection != null && rowMapper != null) {
  630.         int               aRow;
  631.         int               validCount = 0;
  632.         int[]             rows = rowMapper.getRowsForPaths(selection);
  633.  
  634.         for(int counter = 0, maxCounter = selection.length;
  635.         counter < maxCounter; counter++) {
  636.         aRow = rows[counter];
  637.         if(aRow != -1)
  638.             listSelectionModel.addSelectionInterval(aRow, aRow);
  639.         }
  640.         if(leadIndex != -1)
  641.         leadRow = rows[leadIndex];
  642.         insureRowContinuity();
  643.  
  644.     }
  645.     else
  646.         leadRow = -1;
  647.     }
  648.  
  649.     /**
  650.      * Returns the lead selection index. That is the last index that was
  651.      * added.
  652.      */
  653.     public int getLeadSelectionRow() {
  654.     return leadRow;
  655.     }
  656.  
  657.     /**
  658.      * Returns the last path that was added.
  659.      */
  660.     public TreePath getLeadSelectionPath() {
  661.     return leadPath;
  662.     }
  663.  
  664.     /**
  665.      * Add a PropertyChangeListener to the listener list.
  666.      * The listener is registered for all properties.
  667.      * <p>
  668.      * A PropertyChangeEvent will get fired in response to an
  669.      * explicit setFont, setBackground, or SetForeground on the
  670.      * current component.  Note that if the current component is
  671.      * inheriting its foreground, background, or font from its
  672.      * container, then no event will be fired in response to a
  673.      * change in the inherited property.
  674.      *
  675.      * @param listener  The PropertyChangeListener to be added
  676.      */
  677.     public synchronized void addPropertyChangeListener(
  678.                                 PropertyChangeListener listener) {
  679.         if (changeSupport == null) {
  680.             changeSupport = new java.beans.PropertyChangeSupport(this);
  681.         }
  682.         changeSupport.addPropertyChangeListener(listener);
  683.     }
  684.  
  685.     /**
  686.      * Remove a PropertyChangeListener from the listener list.
  687.      * This removes a PropertyChangeListener that was registered
  688.      * for all properties.
  689.      *
  690.      * @param listener  The PropertyChangeListener to be removed
  691.      */
  692.  
  693.     public synchronized void removePropertyChangeListener(
  694.                                 PropertyChangeListener listener) {
  695.         if (changeSupport == null) {
  696.             return;
  697.         }
  698.         changeSupport.removePropertyChangeListener(listener);
  699.     }
  700.  
  701.     /**
  702.      * Useful for CONTIGUOUS_TREE_SELECTION. If the rows that are selected
  703.      * are not contiguous then the selection is reset to be contiguous.
  704.      * Or if the selection mode is single selection and more than one
  705.      * this is selected the selection is reset.
  706.      */
  707.     protected void insureRowContinuity() {
  708.     if(selectionMode == TreeSelectionModel.CONTIGUOUS_TREE_SELECTION &&
  709.        selection != null && rowMapper != null) {
  710.         DefaultListSelectionModel lModel = listSelectionModel;
  711.         int                       min = lModel.getMinSelectionIndex();
  712.  
  713.         if(min != -1) {
  714.         for(int counter = min,
  715.             maxCounter = lModel.getMaxSelectionIndex();
  716.                 counter <= maxCounter; counter++) {
  717.             if(!lModel.isSelectedIndex(counter)) {
  718.             if(counter == min) {
  719.                 clearSelection();
  720.             }
  721.             else {
  722.                 TreePath[]   newSel = new TreePath[counter - min];
  723.  
  724.                 System.arraycopy(selection, 0, newSel,
  725.                          0, counter - min);
  726.                 setSelectionPaths(newSel);
  727.                 break;
  728.             }
  729.             }
  730.         }
  731.         }
  732.     }
  733.     else if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION &&
  734.         selection != null && selection.length > 1) {
  735.         setSelectionPath(selection[0]);
  736.     }
  737.     }
  738.  
  739.     /**
  740.      * Returns true if the paths are contiguous.
  741.      */
  742.     protected boolean arePathsContiguous(TreePath[] paths) {
  743.     if(rowMapper == null || paths.length < 2)
  744.         return true;
  745.     else {
  746.         BitSet                             bitSet = new BitSet(32);
  747.         int                                anIndex, counter, min;
  748.         int                                pathCount = paths.length;
  749.         int                                validCount = 0;
  750.         TreePath[]                         tempPath = new TreePath[1];
  751.  
  752.         tempPath[0] = paths[0];
  753.         min = rowMapper.getRowsForPaths(tempPath)[0];
  754.         for(counter = 0; counter < pathCount; counter++) {
  755.         if(paths[counter] != null) {
  756.             tempPath[0] = paths[counter];
  757.             anIndex = rowMapper.getRowsForPaths(tempPath)[0];
  758.             if(anIndex == -1 || anIndex < (min - pathCount) ||
  759.                anIndex > (min + pathCount))
  760.             return false;
  761.             if(anIndex < min)
  762.             min = anIndex;
  763.             if(!bitSet.get(anIndex)) {
  764.             bitSet.set(anIndex);
  765.             validCount++;
  766.             }
  767.         }
  768.         }
  769.         int          maxCounter = validCount + min;
  770.  
  771.         for(counter = min; counter < maxCounter; counter++)
  772.         if(!bitSet.get(counter))
  773.             return false;
  774.     }
  775.     return true;
  776.     }
  777.  
  778.     /**
  779.      * Returns true if the paths can be added without breaking the
  780.      * continuity of the model.
  781.      */
  782.     protected boolean canPathsBeAdded(TreePath[] paths) {
  783.     if(paths == null || paths.length == 0 || rowMapper == null ||
  784.        selection == null || selectionMode ==
  785.        TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
  786.         return true;
  787.     else {
  788.         BitSet                       bitSet = new BitSet();
  789.         DefaultListSelectionModel    lModel = listSelectionModel;
  790.         int                          anIndex;
  791.         int                          counter;
  792.         int                          min = lModel.getMinSelectionIndex();
  793.         int                             max = lModel.getMaxSelectionIndex();
  794.         TreePath[]                   tempPath = new TreePath[1];
  795.  
  796.         if(min != -1) {
  797.         for(counter = min; counter <= max; counter++) {
  798.             if(lModel.isSelectedIndex(min))
  799.             bitSet.set(counter);
  800.         }
  801.         }
  802.         else {
  803.         tempPath[0] = paths[0];
  804.         min = max = rowMapper.getRowsForPaths(tempPath)[0];
  805.         }
  806.         for(counter = paths.length - 1; counter >= 0; counter--) {
  807.         if(paths[counter] != null) {
  808.             tempPath[0] = paths[counter];
  809.             anIndex = rowMapper.getRowsForPaths(tempPath)[0];
  810.             min = Math.min(anIndex, min);
  811.             max = Math.max(anIndex, max);
  812.             if(anIndex == -1)
  813.             return false;
  814.             bitSet.set(anIndex);
  815.         }
  816.         }
  817.         for(counter = min; counter <= max; counter++)
  818.         if(!bitSet.get(counter))
  819.             return false;
  820.     }
  821.     return true;
  822.     }
  823.  
  824.     /**
  825.      * Returns true if the paths can be removed without breaking the
  826.      * continuity of the model.
  827.      * This is rather expensive.
  828.      */
  829.     protected boolean canPathsBeRemoved(TreePath[] paths) {
  830.     if(rowMapper == null || selection == null ||
  831.        selectionMode == TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
  832.         return true;
  833.     else {
  834.         boolean              found;
  835.         BitSet               bitSet = new BitSet();
  836.         int                  counter, rCounter;
  837.         int                  pathCount = paths.length;
  838.         int                  anIndex;
  839.         int                  min = -1;
  840.         int                  validCount = 0;
  841.         TreePath[]           tPaths = new TreePath[pathCount];
  842.         TreePath[]           tempPath = new TreePath[1];
  843.  
  844.         /* Determine the rows for the removed entries. */
  845.         System.arraycopy(paths, 0, tPaths, 0, pathCount);
  846.         for(counter = selection.length - 1; counter >= 0; counter--) {
  847.         found = false;
  848.         for(rCounter = 0; rCounter < pathCount; rCounter++) {
  849.             if(tPaths[rCounter] != null && selection[counter].
  850.                equals(tPaths[rCounter])) {
  851.             tPaths[rCounter] = null;
  852.             found = true;
  853.             break;
  854.             }
  855.         }
  856.         if(!found) {
  857.             tempPath[0] = selection[counter];
  858.             anIndex = rowMapper.getRowsForPaths(tempPath)[0];
  859.             if(anIndex != -1 && !bitSet.get(anIndex)) {
  860.             validCount++;
  861.             if(min == -1)
  862.                 min = anIndex;
  863.             else
  864.                 min = Math.min(min, anIndex);
  865.             bitSet.set(anIndex);
  866.             }
  867.         }
  868.         }
  869.         /* Make sure they are contiguous. */
  870.         if(validCount > 1) {
  871.         for(counter = min + validCount - 1; counter >= min;
  872.             counter--)
  873.             if(!bitSet.get(counter))
  874.             return false;
  875.         }
  876.     }
  877.     return true;
  878.     }
  879.  
  880.     /**
  881.       * Notifies listeners of a change in path. changePaths should contain
  882.       * instances of PathPlaceHolder.
  883.       * 
  884.       */
  885.     protected void notifyPathChange(Vector changedPaths,
  886.                     TreePath oldLeadSelection) {
  887.     int                    cPathCount = changedPaths.size();
  888.     boolean[]              newness = new boolean[cPathCount];
  889.     TreePath[]            paths = new TreePath[cPathCount];
  890.     PathPlaceHolder        placeholder;
  891.     
  892.     for(int counter = 0; counter < cPathCount; counter++) {
  893.         placeholder = (PathPlaceHolder)changedPaths.elementAt(counter);
  894.         newness[counter] = placeholder.isNew;
  895.         paths[counter] = placeholder.path;
  896.     }
  897.     
  898.     TreeSelectionEvent     event = new TreeSelectionEvent
  899.                       (this, paths, newness, oldLeadSelection, leadPath);
  900.     
  901.     fireValueChanged(event);
  902.     }
  903.  
  904.     /**
  905.      * Updates the leadIndex instance variable.
  906.      */
  907.     protected void updateLeadIndex() {
  908.     if(leadPath != null) {
  909.         if(selection == null) {
  910.         leadPath = null;
  911.         leadIndex = leadRow = -1;
  912.         }
  913.         else {
  914.         leadRow = leadIndex = -1;
  915.         for(int counter = selection.length - 1; counter >= 0;
  916.             counter--) {
  917.             if(selection[counter].equals(leadPath)) {
  918.             leadIndex = counter;
  919.             break;
  920.             }
  921.         }
  922.         }
  923.     }
  924.     }
  925.  
  926.     /**
  927.       * Insures that all the elements in path are unique.  This does not
  928.       * check for a null selection!
  929.       */
  930.     protected void insureUniqueness() {
  931.     int            compareCounter;
  932.     int            dupCount = 0;
  933.     int            indexCounter;
  934.  
  935.     for(compareCounter = 0; compareCounter < selection.length;
  936.         compareCounter++) {
  937.         if(selection[compareCounter] != null) {
  938.         for(indexCounter = compareCounter + 1; indexCounter <
  939.             selection.length; indexCounter++) {
  940.             if (selection[indexCounter] != null &&
  941.             selection[compareCounter].equals(selection
  942.                              [indexCounter])){
  943.             dupCount++;
  944.             selection[indexCounter] = null;
  945.             }
  946.         }
  947.         }
  948.     }
  949.     if(dupCount > 0) {
  950.         /* Squash the duplicates. */
  951.         TreePath[]          newSelection = new TreePath[selection.length-
  952.                                  dupCount];
  953.  
  954.         for (int counter = 0, validCounter = 0; counter < selection.length;
  955.             counter++)
  956.         if(selection[counter] != null)
  957.             newSelection[validCounter++] = selection[counter];
  958.         selection = newSelection;
  959.     }
  960.     }
  961.  
  962.  
  963.     public String toString() {
  964.     int                selCount = getSelectionCount();
  965.     StringBuffer       retBuffer = new StringBuffer();
  966.     int[]              rows;
  967.  
  968.     if(rowMapper != null)
  969.         rows = rowMapper.getRowsForPaths(selection);
  970.     else
  971.         rows = null;
  972.     retBuffer.append(getClass().getName() + " " + hashCode() + " [ ");
  973.     for(int counter = 0; counter < selCount; counter++) {
  974.         if(rows != null)
  975.         retBuffer.append(selection[counter].toString() + "@" +
  976.                  Integer.toString(rows[counter])+ " ");
  977.         else
  978.         retBuffer.append(selection[counter].toString() + " ");
  979.     }
  980.     retBuffer.append("]");
  981.     return retBuffer.toString();
  982.     }
  983.  
  984.     /**
  985.      * Returns a clone of the reciever with the same selection.
  986.      * selectionListeners, and PropertyListeners are not duplicated.
  987.      *
  988.      * @exception CloneNotSupportedException if the receiver does not
  989.      *    both (a) implement the Cloneable interface and (b) define a
  990.      *    <code>clone</code> method.
  991.      */
  992.     public Object clone() throws CloneNotSupportedException {
  993.     DefaultTreeSelectionModel        clone = (DefaultTreeSelectionModel)
  994.                         super.clone();
  995.  
  996.     clone.changeSupport = null;
  997.     if(selection != null) {
  998.         int              selLength = selection.length;
  999.  
  1000.         clone.selection = new TreePath[selLength];
  1001.         System.arraycopy(selection, 0, clone.selection, 0, selLength);
  1002.     }
  1003.     clone.listenerList = new EventListenerList();
  1004.     clone.listSelectionModel = (DefaultListSelectionModel)
  1005.         listSelectionModel.clone();
  1006.     return clone;
  1007.     }
  1008.  
  1009.     // Serialization support.  
  1010.     private void writeObject(ObjectOutputStream s) throws IOException {
  1011.     Object[]             tValues;
  1012.  
  1013.     s.defaultWriteObject();
  1014.     // Save the rowMapper, if it implements Serializable
  1015.     if(rowMapper != null && rowMapper instanceof Serializable) {
  1016.         tValues = new Object[2];
  1017.         tValues[0] = "rowMapper";
  1018.         tValues[1] = rowMapper;
  1019.     }
  1020.     else
  1021.         tValues = new Object[0];
  1022.     s.writeObject(tValues);
  1023.     }
  1024.  
  1025.  
  1026.     private void readObject(ObjectInputStream s) 
  1027.     throws IOException, ClassNotFoundException {
  1028.     Object[]      tValues;
  1029.  
  1030.     s.defaultReadObject();
  1031.  
  1032.     tValues = (Object[])s.readObject();
  1033.  
  1034.     if(tValues.length > 0 && tValues[0].equals("rowMapper"))
  1035.         rowMapper = (RowMapper)tValues[1];
  1036.     }
  1037. }
  1038.  
  1039. /**
  1040.  * Holds a path and whether or not it is new.
  1041.  */
  1042. class PathPlaceHolder {
  1043.     protected boolean             isNew;
  1044.     protected TreePath           path;
  1045.  
  1046.     PathPlaceHolder(TreePath path, boolean isNew) {
  1047.     this.path = path;
  1048.     this.isNew = isNew;
  1049.     }
  1050. }
  1051.